package org.rooster;

import org.jgap.*;
import org.jgap.impl.DefaultConfiguration;
import org.jgap.impl.IntegerGene;
import org.jgap.impl.MutationOperator;
import org.rooster.chromosomeevaluator.RuleEvaluator;
import org.rooster.encoder.Encoder;
import org.rooster.genetic.JGAPFitnessFunctionImplementation;
import org.rooster.genetic.monitor.ProgressWithIterationsMonitor;
import org.rooster.genetic.monitor.ProgressWithTimeMonitor;
import org.rooster.genetic.monitor.TimeMonitor;
import org.rooster.manager.ResourceManager;
import org.rooster.manager.ResourceTypeInterface;
import org.rooster.manager.TaskManager;
import org.rooster.service.ScheduleService;

import java.sql.SQLException;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Created by IntelliJ IDEA.
 * User: manorama
 * Date: 6/3/12
 * Time: 6:42 PM
 * To change this template use File | Settings | File Templates.
 */
public class JGAPMain extends Thread {

    private static double MAX_FITNESS = 35;
    private static int MAX_TIME = 2 * 60;
    private static int NO_OF_ITERATIONS = 1000;
    private static boolean timeMonitor = false;                     //ProgressWithTimeMonitor time
    private static boolean progressWithTimeMonitor = false;         //ProgressWithTimeMonitor progress
    private static boolean fitnessMonitor = false;                  //ProgressWithTimeMonitor fitness
    private static boolean progressWithIterationsMonitor = false;
    private Thread dynamicTableFormThread;
    private RuleEvaluator ruleEvaluator = new RuleEvaluator();
    private static int POPULATION_SIZE = 100;
    private static int MUTATION_RATIO = 45;

    /**
     * 
     */
    @Override
    public void run() {

        long startTime = System.currentTimeMillis();

        try {
            try {
                runGA();
            } catch (SQLException ex) {
                Logger.getLogger(JGAPMain.class.getName()).log(Level.SEVERE, null, ex);
            }
        } catch (InvalidConfigurationException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Time taken = " + (endTime - startTime) / 1000.0 + " seconds");

    }

    private void runGA() throws InvalidConfigurationException, SQLException {

        Configuration defaultConfiguration = new DefaultConfiguration();
        setMutationRate(defaultConfiguration);

        listParameters(defaultConfiguration);
        FitnessFunction scheduleFitness = new JGAPFitnessFunctionImplementation(ruleEvaluator);

        defaultConfiguration.setFitnessFunction(scheduleFitness);

        ResourceManager resourceManager = new ResourceManager();
        TaskManager taskManager = new TaskManager(); //To initialize the resource requirements this line is compulsory

        Gene[] sampleGenes = new IntegerGene[TaskManager.getTotalRequirement()];
        System.out.println("Length of Chromosome = " + sampleGenes.length);

        int geneCount = 0;
        int requirement;

        for (int i = 0; i < TaskManager.NO_OF_SECTIONS; i++) {
            for (int j = 0; j < TaskManager.NO_OF_TIME_SLOTS; j++) {
                for (int k = 0; k < ResourceTypeInterface.NO_OF_RESOURCE_TYPES; k++) {
                    requirement = TaskManager.getResourceTypeRequirement(i, j, k);
                    for (int l = 0; l < requirement; l++) {
                        try {
                            sampleGenes[geneCount++] = new IntegerGene(defaultConfiguration, Encoder.getInstance().lowerLimitForResourceType(k), Encoder.getInstance().upperLimitForResourceType(k));
                        } catch (Exception ex) {
                            Logger.getLogger(JGAPMain.class.getName()).log(Level.SEVERE, null, ex);
                        }

                    }
                }
            }
        }

        Chromosome sampleChromosome = new Chromosome(defaultConfiguration, sampleGenes);
        defaultConfiguration.setSampleChromosome(sampleChromosome);
        defaultConfiguration.setPopulationSize(POPULATION_SIZE);
        Genotype population = Genotype.randomInitialGenotype(defaultConfiguration);


        if (timeMonitor) {
            evolveUntilMaxTimeLimitReach(population, MAX_TIME);
        } else if (fitnessMonitor) {
            evolveUntilRequiredFitness(population, MAX_FITNESS);
        } else if (progressWithTimeMonitor) {
            evolveUntilThereIsNoProgressWithinTime(population, MAX_TIME);
        } else if (progressWithIterationsMonitor) {
            evolveUntilThereIsNoProgressWithinIterations(population, NO_OF_ITERATIONS);
        }
    }

    /**
     * 
     * @return
     */
    public RuleEvaluator getRuleEvaluator() {
        return ruleEvaluator;
    }

    /**
     * Evolve population until the maximum time limit given is reached
     * @param population
     * @param max_time
     */
    private void evolveUntilMaxTimeLimitReach(Genotype population, int max_time) throws SQLException {

        IChromosome bestSolutionSoFar;
        int count = 0;

        TimeMonitor timeMonitor = new TimeMonitor(max_time);
        timeMonitor.start();
        dynamicTableFormThread.start();

        do {
            population.evolve();
            bestSolutionSoFar = population.getFittestChromosome();
            count++;
            System.out.println(bestSolutionSoFar.getFitnessValue());

        } while (!timeMonitor.isMAX_TIME_REACHED());

        System.out.println("No of Iterations = " + count);
        insertRecordsToDatabase(bestSolutionSoFar);


    }

    /**
     * Termination condition for Genetic Algorithm
     * If there is no progress within a given no of iterations
     * Then terminated the evolving
     *
     * @param population
     * @param noOfIterations
     */
    private void evolveUntilThereIsNoProgressWithinIterations(Genotype population, int noOfIterations) throws SQLException {

        IChromosome bestSolutionSoFar;
        int count = 0;
        Vector<Double> fitnessValuesVector = new Vector<Double>(500);

        ProgressWithIterationsMonitor progressWithIterationsMonitor = new ProgressWithIterationsMonitor(fitnessValuesVector, noOfIterations);
        progressWithIterationsMonitor.start();

        do {
            population.evolve();
            bestSolutionSoFar = population.getFittestChromosome();
            count++;

            fitnessValuesVector.add(bestSolutionSoFar.getFitnessValue());

            System.out.println(bestSolutionSoFar.getFitnessValue());

        } while (!progressWithIterationsMonitor.isMAX_PROGRESS_REACHED());

        System.out.println("No of Iterations = " + count);
        insertRecordsToDatabase(bestSolutionSoFar);

    }

    /**
     * Evolove until the required fitness is reached
     * @param population
     */
    private void evolveUntilRequiredFitness(Genotype population, double requiredFitness) throws SQLException {
        IChromosome bestSolutionSoFar;
        int count = 0;

        do {
            population.evolve();
            bestSolutionSoFar = population.getFittestChromosome();
            count++;

            System.out.println(bestSolutionSoFar.getFitnessValue());
        } while (bestSolutionSoFar.getFitnessValue() < requiredFitness);

        System.out.println("No of Iterations = " + count);
        insertRecordsToDatabase(bestSolutionSoFar);
    }

    /**
     * Termination condition for Genetic Algorithm
     * If there is no progress within the specified time then terminate
     * @param population
     * @param timeInSeconds
     */
    private void evolveUntilThereIsNoProgressWithinTime(Genotype population, int timeInSeconds) throws SQLException {
        IChromosome bestSolutionSoFar;
        int count = 0;
        Vector<Double> fitnessValuesVector = new Vector<Double>(500);

        ProgressWithTimeMonitor progressWithTimeMonitor = new ProgressWithTimeMonitor(fitnessValuesVector, timeInSeconds);
        progressWithTimeMonitor.start();

        do {
            population.evolve();
            bestSolutionSoFar = population.getFittestChromosome();
            count++;

            fitnessValuesVector.add(bestSolutionSoFar.getFitnessValue());
            System.out.println(bestSolutionSoFar.getFitnessValue());

        } while (!progressWithTimeMonitor.isMAX_PROGRESS_REACHED());

        System.out.println("No of Iterations = " + count);
        insertRecordsToDatabase(bestSolutionSoFar);
    }

    /**
     * List all the GA parameters
     * @param defaultConfiguration
     */
    private static void listParameters(Configuration defaultConfiguration) {
        List operators = defaultConfiguration.getGeneticOperators();
        Iterator iterator = operators.iterator();
        GeneticOperator geneticOperator;
        while (iterator.hasNext()) {
            geneticOperator = (GeneticOperator) iterator.next();
            System.out.println(geneticOperator);
            if (geneticOperator instanceof MutationOperator) {
                System.out.println("Mutation rate = " + ((MutationOperator) geneticOperator).getMutationRate());
            }
        }

    }

    /**
     * set the mutation rate
     * @param defaultConfiguration
     */
    private void setMutationRate(Configuration defaultConfiguration) {
        List operators = defaultConfiguration.getGeneticOperators();
        Iterator iterator = operators.iterator();
        GeneticOperator geneticOperator;
        while (iterator.hasNext()) {
            geneticOperator = (GeneticOperator) iterator.next();
            if (geneticOperator instanceof MutationOperator) {
                ((MutationOperator) geneticOperator).setMutationRate(MUTATION_RATIO);
            }
        }

    }

    /**
     *
     * @param fitnessSelected
     * @param fitness
     * @param timeSelected
     * @param time
     * @param progressWithinTimeSelected
     * @param progressWithinIterationsSelected
     * @param noOfIterations
     */
    public void setParameters(boolean fitnessSelected, int fitness, boolean timeSelected, int time, boolean progressWithinTimeSelected, boolean progressWithinIterationsSelected, int noOfIterations) {
        timeMonitor = timeSelected;
        fitnessMonitor = fitnessSelected;
        progressWithTimeMonitor = progressWithinTimeSelected;
        progressWithIterationsMonitor = progressWithinIterationsSelected;

        MAX_TIME = time;
        MAX_FITNESS = fitness;
        NO_OF_ITERATIONS = noOfIterations;
    }
    
    
    private void insertRecordsToDatabase(IChromosome bestSolutionSoFar) {
        ScheduleService.insertRecordsToDatabase(bestSolutionSoFar);
    }
}
